package com.bowen.security.rsa.util;

import java.io.ByteArrayOutputStream;
import java.net.URLDecoder;
import java.security.Key;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.Signature;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;

import javax.crypto.Cipher;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.bowen.security.rsa.contant.BaseErrorEnum;
import com.bowen.security.rsa.factory.RasFactoryExcute;
import com.bowen.security.rsa.model.AuthRequest;
import com.bowen.security.rsa.model.AuthResponse;
import com.bowen.util.StringUtils;

import org.apache.commons.codec.binary.Base64;

/**
 * 加密、解密、生成签名
 * 
 * @author Bowen
 * @since 2021-08-17 10:51:44
 */
public class RsaUtil<T> {

  /**
   * 编码格式
   */
  private static final String utf_8 = "UTF-8";

  /**
   * 签名 key
   */
  private static final String sign_key = "sign";

  /**
   * appid key
   */
  private static final String appid_key = "appid";

  /**
   * 解密(包含验证签名)
   * 
   * @param <T>   加密串
   * @param clazz 值对象
   * @param data  数据
   * @return T
   */
  public static <T extends AuthRequest> T decrypt(Class<T> clazz, String data) {
    return decrypt(clazz, data, true);
  }

  /**
   * 加密
   * 
   * @param <T>   泛型
   * @param data  数据
   * @param appid 系统id
   * @return 加密串
   */
  public static <T extends AuthResponse> String encryption(Object data, String appid) {
    return encryption(data, RasFactoryExcute.getPrivatek(), RasFactoryExcute.getPublick(appid));
  }

  /**
   * 解密(包含验证签名)
   * 
   * @param <T>        泛型
   * @param clazz      类
   * @param data       数据
   * @param urlDecoder 解码器
   * @return 值对象
   */
  public static <T extends AuthRequest> T decrypt(Class<T> clazz, String data, boolean urlDecoder) {
    // 私钥解密
    try {
      String decryptStr = RSAUtils.decryptDataOnJava(data, RasFactoryExcute.getPrivatek());
      JSONObject jsonObject = JSON.parseObject(decryptStr);

      // 后端请求接口需要加一个参数：urlHttp为true，表示Java后端接口调用
      String sign = "";
      if (StringUtils.isNotBlank(jsonObject.getString("urlHttp")) && "true".equals(jsonObject.getString("urlHttp"))) {
        // 表示后端Java请求
        sign = jsonObject.remove("sign").toString();
      } else {
        // 默认处理前端js的请求
        sign = urlDecoder ? URLDecoder.decode(jsonObject.remove("sign").toString(), utf_8)
            : jsonObject.remove("sign").toString();
      }

      String appid = jsonObject.getString("appid");
      Optional.ofNullable(appid).orElseThrow(() -> new RuntimeException(BaseErrorEnum.APPID_ERROR.toString()));
      // String publick = RasFactoryExcute.getPublick(appid);
      if (RSAUtils.verify(jsonObject.toJSONString().getBytes(), RasFactoryExcute.getPublick(appid), sign)) {
        return JSON.toJavaObject(jsonObject, clazz);
      } else {
        throw new RuntimeException(BaseErrorEnum.SIGN_ERROR.toString());
      }
    } catch (Exception e) {
      throw new RuntimeException(BaseErrorEnum.ILLEGAL_REQUEST.toString(), e);
    }
  }

  /**
   * 只验证签名.
   * 
   * @param <T>  泛型
   * @param data 数据
   * @return 源数据
   * @throws Exception 验签异常
   */
  public static <T extends AuthRequest> T checkSignBackT(T data) throws Exception {
    boolean checkSign = checkSign(data);
    if (checkSign) {
      return data;
    }
    throw new RuntimeException(BaseErrorEnum.SIGN_ERROR.toString());
  }

  /**
   * 验签
   * 
   * @param <T>  泛型
   * @param data 数据
   * @return 是否成功
   * @throws Exception 验签异常
   */
  public static <T extends AuthRequest> boolean checkSign(T data) throws Exception {
    JSONObject jsonObject = (JSONObject) JSONObject.toJSON(data);
    String sign = jsonObject.remove(sign_key).toString();
    String appid = jsonObject.getString(appid_key);
    return RSAUtils.verify(jsonObject.toJSONString().getBytes(), RasFactoryExcute.getPublick(appid), sign);
  }

  /**
   * 加密(包含加签名)
   * 
   * @param <T>    泛型
   * @param data   数据
   * @param priStr 私钥串
   * @param pubkey 公钥
   * @return String加密串
   */
  public static <T extends AuthResponse> String encryption(Object data, String priStr, String pubkey) {
    try {
      // 私钥生成签名
      JSONObject jsonObject = JSON.parseObject(JSONObject.toJSONString(data));
      jsonObject.remove(sign_key);
      String sign = RSAUtils.sign(jsonObject.toJSONString().getBytes(), priStr);
      jsonObject.put(sign_key, sign);
      System.out.println(jsonObject.toJSONString());

      // 模拟 url编码
      // String encode =
      // java.net.URLEncoder.encode(JSONObject.toJSONString(jsonObject), utf_8);

      String encode = JSONObject.toJSONString(jsonObject);
      // 公钥加密
      return java.util.Base64.getEncoder().encodeToString(RSAUtils.encryptByPublicKey(encode.getBytes(), pubkey));
      // 返回 T
    } catch (Exception e) {
      throw new RuntimeException(BaseErrorEnum.ILLEGAL_REQUEST.toString(), e);
    }

  }

  /**
   * 获取密钥对
   * 
   * @throws Exception 获取密钥对异常
   */
  private static Map<String, String> getkeys() throws Exception {
    Map<String, String> map = new HashMap<String, String>();
    Map<String, Object> keyMap = RSAUtils.genKeyPair();
    map.put(RSAUtils.PRIVATE_KEY, RSAUtils.getPrivateKey(keyMap));
    map.put(RSAUtils.PUBLIC_KEY, RSAUtils.getPublicKey(keyMap));
    return map;
  }

  /**
   * 根据数据生成签名
   * 
   * @param data 数据
   * @return sign
   * @throws Exception 生成签名异常
   */
  public static String getSign(String data) throws Exception {
    return RSAUtils.sign(data.getBytes(), RasFactoryExcute.getPrivatek());
  }

  public static void main(String[] args) throws Exception {
    System.out.println(RsaUtil.getkeys());
  }

  /**
   * rsa算法定义类
   * 
   * @author Bowen
   * @since 2021-08-19 15:20:50
   */
  public static class RSAUtils {
    /**
     * 加密算法RSA
     */
    private static final String KEY_ALGORITHM = "RSA";

    /** */
    /**
     * 签名算法
     */
    private static final String SIGNATURE_ALGORITHM = "MD5withRSA";

    /** */
    /**
     * 获取公钥的key
     */
    private static final String PUBLIC_KEY = "RSAPublicKey";

    /** */
    /**
     * 获取私钥的key
     */
    private static final String PRIVATE_KEY = "RSAPrivateKey";

    /** */
    /**
     * RSA最大加密明文大小
     */
    private static final int MAX_ENCRYPT_BLOCK = 117;

    /** */
    /**
     * RSA最大解密密文大小
     */
    private static final int MAX_DECRYPT_BLOCK = 128;

    /** */
    /**
     * RSA 位数 如果采用2048 上面最大加密和最大解密则须填写: 245 256
     */
    private static final int INITIALIZE_LENGTH = 1024;

    /**
     * <p>
     * 生成密钥对(公钥和私钥)
     * </p>
     * 
     * @return 公钥秘钥对
     * @throws Exception 公钥秘钥对异常
     */
    private static Map<String, Object> genKeyPair() {
      try {
        KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance(KEY_ALGORITHM);
        keyPairGen.initialize(INITIALIZE_LENGTH);
        KeyPair keyPair = keyPairGen.generateKeyPair();
        RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
        RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
        Map<String, Object> keyMap = new HashMap<String, Object>(2);
        keyMap.put(PUBLIC_KEY, publicKey);
        keyMap.put(PRIVATE_KEY, privateKey);
        return keyMap;
      } catch (Exception e) {
        e.printStackTrace();
        System.exit(0);
      }
      return null;
    }

    /**
     * <p>
     * 用私钥对信息生成数字签名
     * </p>
     * 
     * @param data       已加密数据
     * @param privateKey 私钥(BASE64编码)
     * @return 签名
     * @throws Exception 生成签名异常
     */
    private static String sign(byte[] data, String privateKey) throws Exception {
      byte[] keyBytes = java.util.Base64.getDecoder().decode(privateKey);
      PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes);
      KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
      PrivateKey privateK = keyFactory.generatePrivate(pkcs8KeySpec);
      Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM);
      signature.initSign(privateK);
      signature.update(data);
      return Base64.encodeBase64String(signature.sign());
    }

    /** */
    /**
     * <p>
     * 校验数字签名
     * </p>
     * 
     * @param data      已加密数据
     * @param publicKey 公钥(BASE64编码)
     * @param sign      数字签名
     * @return 签名是否通过
     * @throws Exception 验签异常
     * 
     */
    private static boolean verify(byte[] data, String publicKey, String sign) throws Exception {
      byte[] keyBytes = java.util.Base64.getDecoder().decode(publicKey);
      X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
      KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
      PublicKey publicK = keyFactory.generatePublic(keySpec);
      Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM);
      signature.initVerify(publicK);
      signature.update(data);
      return signature.verify(java.util.Base64.getDecoder().decode(sign));
    }

    // 获取公钥
    // private static PublicKey getPublicKey(String key) throws Exception {
    // byte[] keyBytes;
    // // keyBytes = (new BASE64Decoder()).decodeBuffer(key);
    // keyBytes = java.util.Base64.getDecoder().decode(key);
    // X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
    // KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
    // PublicKey publicKey = keyFactory.generatePublic(keySpec);
    // return publicKey;
    // }

    /** */
    /**
     * <P>
     * 私钥解密
     * </p>
     * 
     * @param encryptedData 已加密数据
     * @param privateKey    私钥(BASE64编码)
     * @return 解密字节组
     * @throws Exception 解密异常
     */
    private static byte[] decryptByPrivateKey(byte[] encryptedData, String privateKey) throws Exception {
      byte[] keyBytes = java.util.Base64.getDecoder().decode(privateKey);
      PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes);
      KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
      Key privateK = keyFactory.generatePrivate(pkcs8KeySpec);
      Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
      cipher.init(Cipher.DECRYPT_MODE, privateK);
      int inputLen = encryptedData.length;
      ByteArrayOutputStream out = new ByteArrayOutputStream();
      int offSet = 0;
      byte[] cache;
      int i = 0;
      // 对数据分段解密
      while (inputLen - offSet > 0) {
        if (inputLen - offSet > MAX_DECRYPT_BLOCK) {
          cache = cipher.doFinal(encryptedData, offSet, MAX_DECRYPT_BLOCK);
        } else {
          cache = cipher.doFinal(encryptedData, offSet, inputLen - offSet);
        }
        out.write(cache, 0, cache.length);
        i++;
        offSet = i * MAX_DECRYPT_BLOCK;
      }
      byte[] decryptedData = out.toByteArray();
      out.close();
      return decryptedData;
    }

    /** */
    /**
     * <p>
     * 公钥加密
     * </p>
     * 
     * @param data      源数据
     * @param publicKey 公钥(BASE64编码)
     * @return 加密字节组
     * @throws Exception 加密异常
     */
    private static byte[] encryptByPublicKey(byte[] data, String publicKey) throws Exception {
      byte[] keyBytes = java.util.Base64.getDecoder().decode(publicKey);
      X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(keyBytes);
      KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
      Key publicK = keyFactory.generatePublic(x509KeySpec);
      // 对数据加密
      Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
      cipher.init(Cipher.ENCRYPT_MODE, publicK);
      int inputLen = data.length;
      ByteArrayOutputStream out = new ByteArrayOutputStream();
      int offSet = 0;
      byte[] cache;
      int i = 0;
      // 对数据分段加密
      while (inputLen - offSet > 0) {
        if (inputLen - offSet > MAX_ENCRYPT_BLOCK) {
          cache = cipher.doFinal(data, offSet, MAX_ENCRYPT_BLOCK);
        } else {
          cache = cipher.doFinal(data, offSet, inputLen - offSet);
        }
        out.write(cache, 0, cache.length);
        i++;
        offSet = i * MAX_ENCRYPT_BLOCK;
      }
      byte[] encryptedData = out.toByteArray();
      out.close();
      return encryptedData;
    }

    /**
     * <p>
     * 获取私钥
     * </p>
     * 
     * @param keyMap 密钥对
     * @return 私钥
     * @throws Exception 获取私钥异常
     */
    private static String getPrivateKey(Map<String, Object> keyMap) throws Exception {
      Key key = (Key) keyMap.get(PRIVATE_KEY);
      return Base64.encodeBase64String(key.getEncoded());
    }

    /**
     * <p>
     * 获取公钥
     * </p>
     * 
     * @param keyMap 密钥对
     * @return 公钥
     * @throws Exception 获取公钥异常
     */
    private static String getPublicKey(Map<String, Object> keyMap) throws Exception {
      Key key = (Key) keyMap.get(PUBLIC_KEY);
      return Base64.encodeBase64String(key.getEncoded());
    }

    /**
     * java端公钥加密
     * 
     * @param data      数据
     * @param PUBLICKEY 公钥
     * @return 加密串
     */
    @SuppressWarnings("unused")
    private static String encryptedDataOnJava(String data, String PUBLICKEY) {
      try {
        data = Base64.encodeBase64String(encryptByPublicKey(data.getBytes(), PUBLICKEY));
      } catch (Exception e) {
        e.printStackTrace();
      }
      return data;
    }

    /**
     * 解密
     * 
     * @param data       加密数据
     * @param PRIVATEKEY 私钥
     * @return 解密串
     */
    private static String decryptDataOnJava(String data, String PRIVATEKEY) {
      String temp = "";
      try {
        byte[] bytes = RSAUtils.decryptByPrivateKey(java.util.Base64.getDecoder().decode(data), PRIVATEKEY);
        temp = new String(java.util.Base64.getDecoder().decode(java.util.Base64.getEncoder().encodeToString(bytes)));
      } catch (Exception e) {
        e.printStackTrace();
      }
      return temp;
    }

  }

}